﻿using System;
using System.ComponentModel.Composition;
using System.Security;
using AxeFrog.Data;
using AxeFrog.Net;
using AxeFrog.Net.Mail;
using AxeFrog.Security;
using AxeFrog.Security.Data;

namespace AxeFrog.Web.Handlers
{
	[Export]
	public class StandardAccountHandler
	{
		private readonly ConnectionContextProvider _ccp;
		private readonly IUserRepository _userRepository;
		private readonly IRoleRepository _roleRepository;
		private readonly string _privateKey;
		private readonly string _publicKey;
		private readonly Mailer _mailer;

		[ImportingConstructor]
		protected StandardAccountHandler(
			Factory factory,
			ConnectionContextProvider ccp,
			[Import("ConnectionProviderName")] string connectionProviderName,
			[Import("PrivateKey")] string privateKey,
			[Import("PublicKey")] string publicKey,
			Mailer mailer)
			: this(
				ccp,
				factory.Create<IUserRepository>(connectionProviderName),
				factory.Create<IRoleRepository>(connectionProviderName),
				privateKey)
		{
			_publicKey = publicKey;
			_mailer = mailer;
		}

		protected StandardAccountHandler(
			ConnectionContextProvider ccp,
			IUserRepository userRepository,
			IRoleRepository roleRepository,
			string privateKey)
		{
			_ccp = ccp;
			_userRepository = userRepository;
			_roleRepository = roleRepository;
			_privateKey = privateKey;
		}

		protected ConnectionContext Connect()
		{
			return _ccp.Connect();
		}

		public bool UpdateUser(User user, string signature, string salt)
		{
			if(user.ID > 0)
			{
				using(Connect())
				{
					var checkUser = _userRepository.GetById(user.ID);
					if(checkUser == null)
						return false;
					if(!string.IsNullOrEmpty(checkUser.Password) && !new Encryption().VerifyDigitalSignature(GetSigString(checkUser.ID, salt), signature, _publicKey))
						return false;
					_userRepository.Store(user);
					return true;
				}
			}
			return false;
		}

		public User RegisterNewUser(string name, string username, string password, string email, int timezoneOffset)
		{
			var user = new User
			{
				Name = name,
				Username = username,
				Email = email,
				TimeZoneOffsetHours = timezoneOffset,
				DateCreated = DateTime.UtcNow
			};
			user.SetPassword(password, WebsiteSettings.PublicKey);
			using(Connect())
			{
				if(_userRepository.CheckForOtherUserByUsername(user.Username, 0))
					return null;
				if(_userRepository.CheckForOtherUserByEmail(user.Email, 0))
					return null;
				_userRepository.Store(user);
			}
			return user;
		}

		private string GetSigString(long id, string salt)
		{
			return id.ToString().GetHashCode() + "." + salt;
		}

		public virtual bool VerifyLoginStatus(long userID, string signature, string salt, out User user, out long[] roles, out AccessLevel accessLevel)
		{
			user = null;
			roles = null;
			accessLevel = AccessLevel.Anonymous;
			if(new Encryption().VerifyDigitalSignature(GetSigString(userID, salt), signature, _publicKey))
			{
				using(Connect())
				{
					user = _userRepository.GetById(userID);
					if(user == null || user.Deleted)
						return false;
					roles = _roleRepository.GetByUserID(userID, out accessLevel);
				}
				return true;
			}
			return false;
		}

		public virtual bool Login(string username, string password, string salt, out User user, out long[] roles, out string signature, out AccessLevel accessLevel)
		{
			roles = null;
			signature = null;
			user = null;
			accessLevel = AccessLevel.Anonymous;
			user = _userRepository.GetByUsername(username);
			if(user == null || user.Deleted)
				return false;
			if(user.Password.Length > 0 && !user.IsPasswordCorrect(password, _privateKey))
				return false;
			roles = _roleRepository.GetByUserID(user.ID, out accessLevel);
			signature = new Encryption().CreateDigitalSignature(GetSigString(user.ID, salt), _privateKey);
			return true;
		}

		public bool CheckForOtherUserByName(string name, long currentUserID)
		{
			return _userRepository.CheckForOtherUserByName(name, currentUserID);
		}

		public bool CheckForOtherUserByEmail(string email, long currentUserID)
		{
			return _userRepository.CheckForOtherUserByEmail(email, currentUserID);
		}

		public bool CheckForOtherUserByUsername(string username, long currentUserID)
		{
			return _userRepository.CheckForOtherUserByUsername(username, currentUserID);
		}

		public bool SendAccountEmail(long userID, string subject, string fromName, string fromEmail,
		                             string host, int? port, string mailServerUsername, string mailServerPassword, string template)
		{
			var user = _userRepository.GetById(userID);
			if(user == null)
				return false;
			var body = (template ?? "").ReplaceTokens("[","]", new
            {
                NAME = user.Name,
				EMAIL = user.Email,
                USERNAME = user.Username,
                PASSWORD = user.DecryptPassword(_privateKey)
            });
			_mailer.Send(user.Name, user.Email, subject, body, true);
			return true;
		}

		public bool SendAccountEmail(string userEmail, string subject, string fromName, string fromEmail,
		                             string host, int? port, string mailServerUsername, string mailServerPassword, string template)
		{
			var user = _userRepository.GetByEmail(userEmail);
			if(user == null)
				return false;
			var body = (template ?? "").ReplaceTokens("[","]", new
            {
                NAME = user.Name,
				EMAIL = user.Email,
                USERNAME = user.Username,
                PASSWORD = user.DecryptPassword(_privateKey)
            });
			_mailer.Send(user.Name, user.Email, subject, body, true);
			return true;
		}

		public bool ChangePassword(long userID, string oldPassword, string newPassword)
		{
			using(_ccp.Connect())
			{
				var user = _userRepository.GetById(userID);
				if(user == null)
					return false;
				if(!user.IsPasswordCorrect(oldPassword, _privateKey))
					return false;
				user.SetPassword(newPassword, _publicKey);
				_userRepository.Store(user);
				return true;
			}
		}
	}
}